﻿/*global analogue*//*jslint bitwise: true, browser: true, eqeqeq: true, immed: true, newcap: true, nomen: true, onevar: true, plusplus: true, regexp: true, undef: true, white: true, indent: 4*/

analogue.selectors = (function () {
    var trim = function (string) {
        return string.replace(/^\s+|\s+$/g, '');
    }, detect = analogue.detect, getById = function (id) {
        var element, elements = document.all[id], i;
        for (i = 1; (element = elements[i]); i += 1) {
            if (element.id === id) {
                return element;
            }
        }
        return null;
    }, getElements = function (context, tag, combinator) {
        tag = (tag ? tag : '*').toLowerCase();
        var child, children, collection = [], element, elements, i, ii, j;
        for (i = 0, ii = context.length; i < ii; i += 1) {
            switch (combinator) {
            case '>':
                children = context[i].childNodes;
                for (j = 0; (child = children[j]); j += 1) {
                    if (child.nodeType === 1 && child.nodeName !== '!') {
                        if (tag === '*' || child.nodeName.toLowerCase() === tag) {
                            collection.push(child);
                        }
                    }
                }
                break;
            case '+':
                element = context[i].nextSibling;
                while (element) {
                    if (element.nodeType === 1 && element.nodeName !== '!') {
                        if (tag !== '*' && element.nodeName.toLowerCase() !== tag) {
                            break;
                        }
                        collection.push(element);
                        break;
                    } else {
                        element = element.nextSibling;
                    }
                }
                break;
            default:
                elements = context[i].getElementsByTagName(tag);
                for (j = 0; (element = elements[j]); j += 1) {
                    collection.push(element);
                }
            }
        }
        return collection;
    }, getPrevious = function (element) {
        while ((element = element.previousSibling) && element.nodeType !== 1) {}
        return element;
    }, guid = 0, parse = function (context, selector) {
        context = [context];
        selector = trim(selector.replace(/\s*(>|\+)\s*/g, ' $1'));
        var combinator, element, elements, i, ii, j, k, kk, match, parts, property, token, tokens = selector.split(' ');
        for (i = 0, ii = tokens.length; i < ii; i += 1) {
            token = trim(tokens[i]);
            if ((match = (/^(>|\+)/i).exec(token))) {
                combinator = match[1];
                token = token.replace(combinator, '');
            }
            if (token.indexOf('#') > -1) {
                parts = token.split('#');
                element = document.getElementById(parts[1]);
                if (element && !detect.getElementById && element.id !== parts[1]) {
                    element = getById(parts[1]);
                }
                if (!element || (parts[0] && element.nodeName.toLowerCase() !== parts[0])) {
                    return [];
                }
                context = [element];
            } else if (token.indexOf('.') > -1) {
                parts = token.split('.');
                elements = getElements(context, parts[0], combinator);
                context = [];
                for (j = 0; (element = elements[j]); j += 1) {
                    match = true;
                    for (k = 1, kk = parts.length; k < kk; k += 1) {
                        if (!element.className || !element.className.match(new RegExp('(^|\\s)' + parts[k] + '(\\s|$)'))) {
                            match = false;
                            break;
                        }
                    }
                    if (match) {
                        context.push(element);
                    }
                }
            } else if ((match = (/^(\w*):first\-child$/i).exec(token))) {
                elements = getElements(context, match[1], combinator);
                context = [];
                for (j = 0; (element = elements[j]); j += 1) {
                    if (!getPrevious(element)) {
                        context.push(element);
                    }
                }
            } else if ((match = (/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/i).exec(token))) {
                elements = getElements(context, match[1], combinator);
                context = [];
                for (j = 0; (element = elements[j]); j += 1) {
                    if (!(property = element.getProperty(match[2]))) {
                        continue;
                    } else if (match[3] === '=' && property !== match[4]) {
                        continue;
                    } else if (match[3] === '~' && !property.match(new RegExp('(^|\\s)' + match[4] + '(\\s|$)'))) {
                        continue;
                    } else if (match[3] === '^' && property.indexOf(match[4]) !== 0) {
                        continue;
                    } else if (match[3] === '$' && property.lastIndexOf(match[4]) !== (property.length - match[4].length)) {
                        continue;
                    } else if (match[3] === '*' && !(property.indexOf(match[4]) + 1)) {
                        continue;
                    } else if (match[3] === '|' && !property.match(new RegExp('^' + match[4] + '-'))) {
                        continue;
                    }
                    context.push(element);
                }
            } else {
                context = getElements(context, token, combinator);
            }
        }
        return context;
    }, resolve = function (rule) {
        var index = 1, last = null, match, parts = ['//', '*'];
        while (rule.length && rule !== last) {
            last = rule;
            rule = trim(rule);
            if (!rule.length) {
                break;
            }
            if ((match = (/^([#.]?)([a-z0-9\\*_\-]*)((\|)([a-z0-9\\*_\-]*))?/i).exec(rule))) {
                if (!match[1]) {
                    if (match[5]) {
                        parts[index] = match[5];
                    } else if (match[2] !== '') {
                        parts[index] = match[2];
                    }
                } else if (match[1] === '#') {
                    parts.push('[@id="' + match[2] + '"]');
                } else if (match[1] === '.') {
                    parts.push('[contains(concat(" ", @class, " "), " ' + match[2] + ' ")]');
                }
                rule = rule.slice(match[0].length);
            }
            if ((match = (/^\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\s*\]/i).exec(rule))) {
                switch (match[2]) {
                case '~':
                    parts.push('[contains(concat(" ", @' + match[1] + ', " "), " ' + match[3] + ' ")]');
                    break;
                case '^':
                    parts.push('[starts-with(@' + match[1] + ', "' + match[3] + '")]');
                    break;
                case '$':
                    parts.push('[substring(@' + match[1] + ', (string-length(@' + match[1] + ') - ' + (match[3].length - 1) + '), ' + match[3].length + ') = "' + match[3] + '"]');
                    break;
                case '*':
                    parts.push('[contains(@' + match[1] + ', "' + match[3] + '")]');
                    break;
                case '|':
                    parts.push('[starts-with(@' + match[1] + ', "' + match[3] + '-")]');
                    break;
                case '=':
                    parts.push('[@' + match[1] + '="' + match[3] + '"]');
                    break;
                default:
                    parts.push('[@' + match[1] + ']');
                }
                rule = rule.slice(match[0].length);
            }
            if ((match = (/^:first\-child/i).exec(rule))) {
                parts.push('[not(preceding-sibling::*)]');
                rule = rule.slice(match[0].length);
            }
            while ((match = (/^:([a-z_\-])+/i).exec(rule))) {
                rule = rule.slice(match[0].length);
            }
            if ((match = (/^(\s*[>+\s])?/i).exec(rule)) && match[0].length) {
                if (match[0].indexOf('>') !== -1) {
                    parts.push('/child::');
                } else if (match[0].indexOf('+') !== -1) {
                    parts.push('/following-sibling::*[1]/self::');
                } else {
                    parts.push('//');
                }
                index = parts.length;
                parts.push('*');
                rule = rule.slice(match[0].length);
            }
            if ((match = (/^\s*,/i).exec(rule))) {
                parts.push(' | ', '//', '*');
                index = parts.length - 1;
                rule = rule.slice(match[0].length);
            }
        }
        return parts.join('');
    }, unique = function (collection) {
        var element, filtered = [], i;
        for (i = 0; (element = collection[i]); i += 1) {
            if (!element.included && element.nodeName !== '!') {
                element.included = true;
                filtered.push(element);
            }
        }
        for (i = 0; (element = filtered[i]); i += 1) {
            element.included = null;
        }
        return filtered;
    };
    return {
        match: (function () {
            if (document.querySelector) {
                return function (context, selector) {
                    var collection = [], elements = context.querySelectorAll(selector), i = 0;
                    if (detect.arraySlice) {
                        return Array.prototype.slice.call(elements);
                    }
                    while (elements[i]) {
                        collection.push(elements[i]);
                        i += 1;
                    }
                    return collection;
                };
            }
            if (document.evaluate) {
                return function (context, selector) {
                    if (context !== document) {
                        if (context.id === '') {
                            context.setProperty('id', context.tagName.toLowerCase() + (guid += 1));
                        }
                        selector = '#' + context.id + ' ' + selector;
                    }
                    var collection = [], element, elements = document.evaluate(resolve(selector), document, null, 0, null);
                    while ((element = elements.iterateNext())) {
                        collection.push(element);
                    }
                    return collection;
                };
            }
            return function (context, selector) {
                var collection = [], i, ii, groups = selector.split(',');
                for (i = 0, ii = groups.length; i < ii; i += 1) {
                    collection = collection.concat(parse(context, groups[i]));
                }
                return unique(collection);
            };
        }())
    };
}());